using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Gj.Galaxy.Logic;
using Gj.MapData.Strategy;
using MessagePack;
using UnityEngine;

namespace Gj.MapData
{
    public class OperateBuilder
    {
        public static ConcurrentQueue<OperateBuilder> Queue = new();
        internal readonly List<OperateData> _all = new();
        private int _alloc;
        internal readonly List<UpdateOperateContent> _contents = new();
        internal UpdateOperateData _current;

        internal StandardExecutor _executor;
        internal DefineData _from;

        internal OperateMessage _operateMessage;
        internal DefineData _target;

        public string MapNoId { get; private set; }

        public static OperateBuilder Get()
        {
            OperateBuilder builder = null;
            if (Queue.TryDequeue(out builder))
            {
            }

            if (builder == null) builder = new OperateBuilder();
            return builder;
        }

        public static OperateBuilder Get(StandardExecutor executor)
        {
            var builder = Get();

            builder.BindExecutor(executor);
            return builder;
        }

        internal static void Release(OperateBuilder builder)
        {
            if (builder._alloc == 0)
            {
                builder._all.Clear();
                builder._contents.Clear();
                builder._from = null;
                builder._current = null;
                builder._operateMessage = null;
                builder._executor = null;
                builder.MapNoId = null;
                Queue.Enqueue(builder);
            }
            else
            {
                builder._alloc--;
            }
        }

        public OperateBuilder Alloc()
        {
            _alloc++;
            return this;
        }

        public EventBuilder ToEvent()
        {
            var eventBuilder = EventBuilder.Get(_executor);

            eventBuilder.Operate(this);
            return eventBuilder;
        }

        public OperateBuilder BindExecutor(StandardExecutor executor)
        {
            _executor = executor;
            MapNoId = executor.CurrentMapNoId;
            return this;
        }

        public OperateBuilder BindMapNoId(string mapNoId)
        {
            MapNoId = mapNoId;
            return this;
        }

        public OperateBuilder Append(OperateBuilder other)
        {
            other.Update();
            if (other._all.Count > 0)
                for (var i = 0; i < other._all.Count; i++)
                    _all.Add(other._all[i]);
            Release(other);
            return this;
        }

        private void Update()
        {
            if (_contents.Count > 0 && _current != null)
            {
                _current.Operates = _contents.ToArray();
                _all.Add(new OperateData
                {
                    Operate = MapData.Operate.BatchUpdate,
                    Content = _current
                });
                _contents.Clear();
            }
        }

        internal OperateData[] Data()
        {
            Update();
            var data = _all.ToArray();
            Release(this);
            return data;
        }

        internal UpdateOperateContent[] DataOfUpdate()
        {
            var data = _contents.ToArray();
            Release(this);
            return data;
        }

        internal OperateContent Content()
        {
            Update();
            var content = new OperateContent();
            content.From = _from;
            content.MapNoId = MapNoId;
            content.Message = _operateMessage;
            content.Operates = _all.ToArray();
            Release(this);
            return content;
        }

        public OperateBuilder From(DefineData from)
        {
            _from = from;
            return this;
        }

        public OperateBuilder Target(DefineData target)
        {
            _target = target;
            return this;
        }

        public OperateBuilder SendMessage(DefineData data, byte Event, byte[] position = null,
            SubmitData content = null, object[] param = null)
        {
            _operateMessage = new OperateMessage();
            _operateMessage.Message = new DataMessage
            {
                Event = Event,
                Content = content,
                Params = param
            };
            _operateMessage.Position = position;
            return this;
        }

        public void Operate(long delay, CheckBuilder check = null)
        {
            Update();
            if (string.IsNullOrEmpty(MapNoId)) Debug.LogError("map empty");
            _executor.Operate(this, check, delay);
            Release(this);
        }

        public static CreateOperateData CreateData(DefineData from, DataType dataType, object id, PositionInt position,
            ExtendContent Extends, SubmitKeyData Infos, Dictionary<string, DependItem> dependents,
            List<EventItem> events)
        {
            var create = new CreateOperateData();
            create.From = from;
            create.Data = new BaseData { DataType = dataType, Id = id };
            create.Position = position;

            create.InfoKey = Infos.Keys();
            create.Infos = Infos;
            if (Extends != null)
            {
                create.ExtendKey = Extends.Keys();
                create.Extends = Extends;
            }

            if (dependents == null) dependents = new Dictionary<string, DependItem>();
            create.Dependents = dependents;
            create.Events = events?.ToArray();
            return create;
        }

        public static ResetOperateData ResetData(DefineData from, DataType dataType, object id, SubmitKeyData Infos,
            Dictionary<string, DependItem> dependents, List<EventItem> events, string scene)
        {
            var reset = new ResetOperateData();
            reset.From = from;
            reset.Data = new BaseData { DataType = dataType, Id = id };

            if (Infos != null)
            {
                reset.InfoKey = Infos.Keys();
                reset.Infos = Infos;
            }

            if (dependents == null) dependents = new Dictionary<string, DependItem>();
            reset.Dependents = dependents;
            reset.Events = events?.ToArray();
            reset.Scene = scene;

            return reset;
        }

        public OperateBuilder Create(DefineData from, DataType dataType, object id, PositionInt position,
            ExtendContent Extends, SubmitKeyData Infos, Dictionary<string, DependItem> dependents,
            List<EventItem> events)
        {
            _all.Add(new OperateData
            {
                Operate = MapData.Operate.Create,
                Content = CreateData(from, dataType, id, position, Extends, Infos, dependents, events)
            });
            return this;
        }

        public OperateBuilder Reset(DefineData from, DataType dataType, object id, SubmitKeyData Infos,
            Dictionary<string, DependItem> dependents, List<EventItem> events, string scene)
        {
            _all.Add(new OperateData
            {
                Operate = MapData.Operate.Reset,
                Content = ResetData(from, dataType, id, Infos, dependents, events, scene)
            });
            return this;
        }

        public OperateBuilder Delete(DefineData from, DataType dataType, object id)
        {
            var delete = new DeleteOperateData();
            delete.From = from;
            delete.DataType = dataType;
            delete.Id = id;
            _all.Add(new OperateData
            {
                Operate = MapData.Operate.Delete,
                Content = delete
            });
            return this;
        }

        public OperateBuilder Delete(DefineData data, CheckBuilder check = null)
        {
            var delete = new DeleteOperateData();
            delete.DataType = data.GetDataType();
            delete.Id = data.GetId();
            _all.Add(new OperateData
            {
                Operate = MapData.Operate.Delete,
                Content = delete
            });
            return this;
        }

        public OperateBuilder Event(DefineData data, EventBuilder eventBuilder)
        {
            var eventOperate = eventBuilder.ToOperate();
            eventOperate.Data = data;
            _all.Add(new OperateData
            {
                Operate = MapData.Operate.Event,
                Content = eventOperate
            });
            return this;
        }

        public OperateBuilder Update(DefineData data, DefineData from = null, CheckBuilder check = null)
        {
            Update();
            _current = new UpdateOperateData();
            _current.From = from;
            _current.Data = data;
            _current.Checks = check;
            return this;
        }

        public OperateBuilder ChangeData(string key, SubmitData value)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ChangeData;
            content.Key = key;
            content.Value = value?.Object();
            _contents.Add(content);
            return this;
        }

        public OperateBuilder ChangeData(string key, object value)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ChangeData;
            content.Key = key;
            content.Value = value;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder ChangeDateTime(string key, int delay)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ChangeDateTime;
            content.Key = key;
            content.Value = delay;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder ChangePosition(Vector2Int position)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ChangePosition;
            content.Value = SerializeInt.Vector2Byte(position);
            _contents.Add(content);
            return this;
        }

        public OperateBuilder ChangeIgnore(bool ignore)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ChangeIgnore;
            content.Value = ignore;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder ChangeInherit(string inheritKey, DataType dataType, object id)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ChangeInherit;
            content.Value = id;
            content.Index = dataType.Value();
            content.Key = inheritKey;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder ChangeDataGreater(string key, int value)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ChangeDataGreater;
            content.Value = value;
            content.Key = key;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder ChangeDataLesser(string key, int value)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ChangeDataLesser;
            content.Value = value;
            content.Key = key;
            _contents.Add(content);
            return this;
        }


        public OperateBuilder ChangeDataGreaterSync(string key, int value)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ChangeDataGreaterSync;
            content.Value = value;
            content.Key = key;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder ChangeDataLesserSync(string key, int value)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ChangeDataLesserSync;
            content.Value = value;
            content.Key = key;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder IncrData(string key, int value)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.IncrData;
            content.Key = key;
            content.Value = value;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder IncrDataSync(string key, int value)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.IncrDataSync;
            content.Key = key;
            content.Value = value;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder IncrDataGreater(string key, int value, int greater)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.IncrDataGreater;
            content.Key = key;
            content.Value = value;
            content.Index = greater;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder IncrDataLesser(string key, int value, int lesser)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.IncrDataLesser;
            content.Key = key;
            content.Value = value;
            content.Index = lesser;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder IncrDataGreaterSync(string key, int value, int greater)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.IncrDataGreaterSync;
            content.Key = key;
            content.Value = value;
            content.Index = greater;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder IncrDataLesserSync(string key, int value, int lesser)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.IncrDataLesserSync;
            content.Key = key;
            content.Value = value;
            content.Index = lesser;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder ChangeList(string extend, int index, object value)
        {
            if (index < 0) return this;
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ChangeList;
            content.Key = extend;
            content.Index = index;
            content.Value = value;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder ChangeListTime(string extend, int index, int second)
        {
            if (index < 0) return this;
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ChangeListTime;
            content.Key = extend;
            content.Index = index;
            content.Value = second;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder IncrList(string extend, int index, int value)
        {
            if (index < 0) return this;
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.IncrList;
            content.Key = extend;
            content.Index = index;
            content.Value = value;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder IncrListFrom(string extend, int index, int from)
        {
            if (index < 0) return this;
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.IncrListFrom;
            content.Key = extend;
            content.Index = index;
            content.Value = from;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder AddSet(string extend, int value)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.AddSet;
            content.Key = extend;
            content.Index = 0;
            content.Value = value;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder RemoveSet(string extend, int value)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.RemoveSet;
            content.Key = extend;
            content.Index = 0;
            content.Value = value;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder AddSorted(string extend, object value, int sort)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.AddSorted;
            content.Key = extend;
            content.Index = sort;
            content.Value = value;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder RemoveSorted(string extend, object value)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.RemoveSorted;
            content.Key = extend;
            content.Value = value;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder AddTimeSorted(string extend, object value, int delaySecond)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.AddTimeSorted;
            content.Key = extend;
            content.Index = delaySecond;
            content.Value = value;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder RemoveTimeSorted(string extend, object value, int number = 0)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.RemoveTimeSorted;
            content.Key = extend;
            content.Value = value;
            content.Index = number;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder AddDepend(string dependKey, DataType dataType, object id)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.AddDepend;
            content.Key = dependKey;
            content.Index = dataType.Value();
            content.Value = id;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder RemoveDepend(string dependKey, DataType dataType, object id)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.RemoveDepend;
            content.Key = dependKey;
            content.Index = dataType.Value();
            content.Value = id;
            _contents.Add(content);
            return this;
        }

        // 重置任务时间
        public OperateBuilder AddTask(TaskContent task, float time = 0, object[] param = null)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.AddTask;
            content.Key = Convert.ToString(MessagePackSerializer.Serialize(task.Content.Object()));
            content.Value = MessagePackSerializer.Serialize(param);
            content.Index = task.Task;

            _contents.Add(content);
            return this;
        }

        // public OperateBuilder UpdateNode(Vector2Int position, int identity, byte type)
        // {
        //     var content = new UpdateOperateContent();
        //     content.Operate = UpdateOperate.UpdateNode;
        //     content.Key = Convert.ToString(type);
        //     content.Value = SerializeInt.Vector2Byte(position);
        //     content.Index = identity;
        //     
        //     _contents.Add(content);
        //     return this;
        // }

        // public OperateBuilder UpdateFixed(Vector2Int[] positions, int identity, byte type)
        // {
        //     var content = new UpdateOperateContent();
        //     content.Operate = UpdateOperate.UpdateFixed;
        //     content.Key = Convert.ToString(type);
        //     content.Value = SerializeInt.Vector2Byte(positions);
        //     content.Index = identity;
        //     
        //     _contents.Add(content);
        //     return this;
        // }

        // public OperateBuilder UpdateSeat(Vector2Int start, int identity)
        // {
        //     var content = new UpdateOperateContent();
        //     content.Operate = UpdateOperate.UpdateSeat;
        //     content.Key = Convert.ToString(SerializeInt.Vector2Byte(start));
        //     content.Index = identity;
        //     
        //     _contents.Add(content);
        //     return this;
        // }
        // 重置任务时间
        public OperateBuilder ResetSchedule(string key, float time = 0, int delay = 0)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ResetSchedule;
            content.Key = key;
            content.Value = (int)(time * 1000);
            content.Index = delay * 1000;
            _contents.Add(content);
            return this;
        }

        // 保持任务时间，但需要重新处理
        public OperateBuilder HoldSchedule(string key, ScheduleBuilder schedule)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.HoldSchedule;
            content.Key = key;
            content.Value = MessagePackSerializer.Serialize(schedule.Data());
            _contents.Add(content);
            return this;
        }

        public OperateBuilder RemoveSchedule(string key)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.RemoveSchedule;
            content.Key = key;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder BindEvent(EventData eventData, byte e)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.BindEvent;
            content.Key = MessagePackSerializer.Serialize(eventData.Content.Object()).ToString();
            content.Index = (byte)eventData.Mode;
            content.Value = e;
            _contents.Add(content);
            return this;
        }

        //
        // public OperateBuilder LinkEvent(byte e, SystemEvent ee)
        // {
        //     var content = new UpdateOperateContent();
        //     content.Operate = UpdateOperate.LinkEvent;
        //     content.Index = ee.Value();
        //     content.Value = e;
        //     _contents.Add(content);
        //     return this;
        // }
        //
        public OperateBuilder ClearEvent(byte e)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.ClearEvent;
            content.Value = e;
            _contents.Add(content);
            return this;
        }
        //
        // public OperateBuilder UnLinkEvent(SystemEvent ee)
        // {
        //     var content = new UpdateOperateContent();
        //     content.Operate = UpdateOperate.UnLinkEvent;
        //     content.Index = ee.Value();
        //     _contents.Add(content);
        //     return this;
        // }

        public OperateBuilder AddRelation(string relation, DataType dataType, object id)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.AddRelation;
            content.Index = dataType.Value();
            content.Value = id;
            content.Key = relation;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder RemoveRelation(string relation, DataType dataType, object id)
        {
            var content = new UpdateOperateContent();
            content.Operate = UpdateOperate.RemoveRelation;
            content.Index = dataType.Value();
            content.Value = id;
            content.Key = relation;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder AddLink(string relation, DataType dataType, object id, bool reverse = false)
        {
            var content = new UpdateOperateContent();
            content.Operate = reverse ? UpdateOperate.AddToLink : UpdateOperate.AddLink;
            content.Index = dataType.Value();
            content.Value = id;
            content.Key = relation;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder RemoveLink(string relation, DataType dataType, object id, bool reverse = false)
        {
            var content = new UpdateOperateContent();
            content.Operate = reverse ? UpdateOperate.RemoveFromLink : UpdateOperate.RemoveLink;
            content.Index = dataType.Value();
            content.Value = id;
            content.Key = relation;
            _contents.Add(content);
            return this;
        }

        public OperateBuilder UpdateMessage(byte Event, byte[] position = null, SubmitData content = null,
            object[] param = null)
        {
            var message = new DataMessage();
            message.Event = Event;
            message.Content = content;
            message.Params = param;
            _current.Message = message;
            return this;
        }
    }
}